package com.tjs.lucky.application.process;

import cn.hutool.core.util.BooleanUtil;
import com.db.router.annotation.MultiDSTransaction;
import com.tjs.lucky.application.mq.RabbitMQProducer;
import com.tjs.lucky.common.*;
import com.tjs.lucky.domain.activity.model.vo.DisAwardInvoiceVO;
import com.tjs.lucky.domain.activity.repository.IActivityRepository;
import com.tjs.lucky.domain.activity.repository.IUserTakeActivityRepository;
import com.tjs.lucky.domain.activity.service.partake.IActivityPartake;
import com.tjs.lucky.domain.award.repository.IAwardRepository;
import com.tjs.lucky.domain.strategy.model.vo.DrawAwardVO;
import com.tjs.lucky.domain.strategy.repository.IStrategyRepository;
import com.tjs.lucky.domain.support.ids.DefindeIdGenerator;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFutureCallback;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.tjs.lucky.common.Constants.DIS_AWARD_QUEUE_NAME;

/**
 * @author TJS
 * @Description 开奖流程
 * @create 2023/7/15 15:53
 */
@Service
@Slf4j
public class OpenAwardProcess {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IAwardRepository awardRepository;

    @Resource
    private IUserTakeActivityRepository userTakeActivityRepository;

    @Resource
    private IActivityPartake activityPartake;

    @Resource
    private RabbitMQProducer rabbitMqProducer;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private Map<Ids, DefindeIdGenerator> idGeneratorMap;

    public OpenAwardProcess() {
    }

    @MultiDSTransaction
    public void deductUserLeftCount(Long activityId,DrawAwardVO drawAwardVO) throws Exception {
        boolean success = userTakeActivityRepository.deductUserTakeCount(activityId, drawAwardVO.getUId());
        if(!success){
            throw new Exception("扣除个人参与次数失败");
        }
        userTakeActivityRepository.saveUserStrategyExport(drawAwardVO,activityId);
    }

    /**
     * 检查Redis是否有对应的缓存，并添加缓存
     *
     * @param activityId
     */
    public void checkRedisAndSet(Long activityId) {

        Boolean inRedis = this.activityAwardInRedis(activityId);
        if (BooleanUtil.isTrue(inRedis)) {
            return;
        }
        RLock lock = redissonClient.getLock("Lucky:openAward:lock:" + activityId);
        if (lock.tryLock()) {
            //双重检验
            if (BooleanUtil.isTrue(this.activityAwardInRedis(activityId))) {
                return;
            }
            this.activityAwardAddToRedis(activityId);
            log.info("checkRedisAndSet：重建缓存成功 , activityId:{}", activityId);
        }
    }

    /**
     * 活动奖品数据是否在Redis中
     *
     * @param activityId
     * @return
     */
    private Boolean activityAwardInRedis(Long activityId) {
        return stringRedisTemplate.hasKey("activityAward:" + activityId);
    }

    /**
     * 将 奖品数据缓存到Redis中,设置一天的缓存时间【默认】
     *
     * @param activityId
     */
    private void activityAwardAddToRedis(Long activityId) {
        Map<String, String> idCountMap = awardRepository.queryAwardList(activityId);
        String key = "awardInfoList:" + activityId;
        stringRedisTemplate.opsForHash().putAll(key, idCountMap);
        stringRedisTemplate.expire(key, 1, TimeUnit.DAYS);
    }

    /**
     * 库存回退
     *
     * @param activityId
     * @param awardId
     */
    public void redisStockBack(Long activityId, String awardId) {
        //TODO:回退的有点多了啊
        try {
            String awardStockListKey = "awardInfoList:" + activityId;
            String activityStockKey = "activityTotalCount:" + activityId;
            String awardTotalCountKey = "awardTotalStockCount:" + activityId;
            stringRedisTemplate.opsForValue().increment(activityStockKey, 1);
            stringRedisTemplate.opsForValue().increment(awardTotalCountKey, 1);
            stringRedisTemplate.opsForHash().increment(awardStockListKey, awardId, 1);
            log.info("库存回退成功！activityId:{},awardId:{}", activityId, awardId);
        } catch (Exception e) {
            log.info("库存回退失败！activityId:{},awardId:{}", activityId, awardId);
        }
    }

    /**
     * 发送MQ消息 去发奖和同步库存
     *
     * @param drawAwardVO
     */
    public void sendMqToDisAward(DrawAwardVO drawAwardVO) {
        // 发奖
        DisAwardInvoiceVO disAwardInvoiceVO = buildInvoiceVO(drawAwardVO);
        rabbitMqProducer.sendMessageToLuckyExchange(
                DIS_AWARD_QUEUE_NAME, disAwardInvoiceVO, "award",
                new ListenableFutureCallback<CorrelationData.Confirm>() {
                    @Override
                    public void onFailure(Throwable e) {
                        activityPartake.updateInvoiceMqState(disAwardInvoiceVO.getUId(), disAwardInvoiceVO.getOrderId(), MQState.PRODUCT_FAIL.getCode());
                        log.info("异步发奖：消息发送失败。原因：{}", e.getMessage());
                    }

                    @Override
                    public void onSuccess(CorrelationData.Confirm confirm) {
                        log.info("异步发奖：消息发送成功!");
                    }
                }
        );
    }

    private DisAwardInvoiceVO buildInvoiceVO(DrawAwardVO drawAwardVO) {
        long orderId = idGeneratorMap.get(Ids.SnowFlake).nextId();
        DisAwardInvoiceVO disAwardInvoiceVO = new DisAwardInvoiceVO();
        disAwardInvoiceVO.setUId(drawAwardVO.getUId());
        disAwardInvoiceVO.setOrderId(orderId);
        disAwardInvoiceVO.setAwardId(drawAwardVO.getAwardId());
        disAwardInvoiceVO.setAwardType(drawAwardVO.getAwardType());
        disAwardInvoiceVO.setAwardName(drawAwardVO.getAwardName());
        disAwardInvoiceVO.setAwardContent(drawAwardVO.getAwardContent());
        disAwardInvoiceVO.setShippingAddress(null);
        disAwardInvoiceVO.setExtInfo(null);
        return disAwardInvoiceVO;
    }

}
